文章目录
  1. 1. Context接口和作用
  2. 2. Context默认实现
  3. 3. Context使用中需要注意的问题

我们在开发过程中会遇到很多场景需要Context, 包括但不限于: 获取资源, 创建View, 显示UI元素… 稍微追踪一下知道Context是一个接口, 当然从名字知道它是提供环境信息的接口, 今天我们就来看看, Context以及它的衍生类.

Context接口和作用

位于frameworks/base/core/java/android/content/Context.java, 是一个抽象类, 它定义了所有可用的接口, 在AS中查看它的类继承关系, 可以发现它有MockContextContextWrapper两个直接子类, 继续追下去会发现它有数量不少的间接子类, Application Activity Service都是它的子类, 这也正是在这些类中用到Context参数时可以直接传this的原因. 我们来看Context定义了哪些接口, 接口及其说明在官网Context页都有说明, 我这里只是相当于翻译一下. 大致上可以分为两类:

  1. 全局信息资源接口及其配套常量. 包括但不限于以下接口:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    getResources 返回Resources实例, 提供应用包的资源管理接口.  
    getAssets 返回AssetManager实例, 它提供资源管理接口. 它管理的资源应当和getResources管理的资源保持一致, 即它们维护同一套资源.
    getPackageManager 返回PackageManager实例, 提供全局包信息.
    getContentResolver 返回ContentResolver实例, 提供内容解析接口.
    getText 封装Resources.getText
    getString 封装Resources.getString
    getClassLoader 获取ClassLoader实例, 可以由此获取包内所有类
    getPackageName 获取应用包的包名
    getSystemService 获取系统服务, 系统服务的名字以常量形式存在Context类中
  2. 公共方法及其配套常量. 包括但不限于:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    getMainLooper 返回当前Context的Looper实例  
    getApplicationContext 获取应用的Context实例, 它不同于当前Context实例, 而是一个属于当前进程的, 全局的, 单例的, Application对象, 这意味着它的生命周期不同于当前Context, 而是跟当前进程捆绑
    registerComponentCallbacks 注册组件回调
    unregisterComponentCallbacks 注销组件回调
    deleteFile 删除与当前应用包关联的私有文件
    deleteDatabase 删除与当前应用包关联的私有SQLiteDatabase数据库
    startActivity 开始一个Activity
    registerReceiver 注册广播接收器
    unregisterReceiver 注销广播接收器
    bindService 绑定服务
    unbindService 解绑服务
    startService 开始服务
    stopService 停止服务
    createPackageContext 以给定应用包名创建该应用的Context

Context默认实现

context关系
前文说了, ActivityApplication都是Context的间接子类. 但是他们处于不同层级, 拥有不同的继承链, Application继承了ContextWrapper(Service也继承了它), 而Activity继承了ContextThemeWrapper(它是ContextWrapper的直接子类). 在ContextWrapper中提供了大多数方法的默认实现, 正如其名, 它只是包装了一个Context实现, 这个实现就是它的mBase成员变量, 是通过构造函数或者attachBaseContext传入的Context实例. 在应用启动过程中, performLaunchActivity会将一个ContextImpl实例作为参数传入Activity.attach方法, 该方法就用这个实例调用attachBaseContext设置好Context. 换句话说, Activity所拥有的Context就是一个随Activity生命周期的ContextImpl实例, Activity中对Context接口的调用, 中途经过ContextThemeWrapperContextWrapper, 最终到达这个mBase指向的ContextImpl的相应接口调用. 也就是说, 它就是我们要找的Activity Context默认实现.
那么Application Context呢? 它调用Context接口时最终同样会传达到mBase上, 它的mBase对象又是什么呢? 我们考察应用启动过程, 发现在performLaunchActivity中, 有一步是makeApplication, 返回一个Application对象. 该方法属于LoadedApk类, 打开该类查看, 发现它维护了一个Application mApplication成员变量, makeApplication方法在该变量非空时直接返回该变量(意味着无论调用多少次, 都返回同一个Application实例), 否则新建一个ContextImpl对象, 并以它为参数之一, 调用mActivityThread.mInstrumentation.newApplication返回一个Application对象. 再查看newApplication方法, 它位于frameworks/base/core/java/android/app/Instrumentation.java中, 其行为是使用给定的ClassLoader先装载给定的类(这里是LoadedApk.makeApplication中字符串appClass代表的Application类, 如果没有指定或强制使用默认appClass, 则该值为”android.app.Application”), 再用该类对象的newInstance创建实例并调用attach方法绑定Context实例. 可见, Application的Context仍然是ContextImpl实例, 不同的是它的生命周期与Application一致.
ContextImpl类中对Context大多数接口方法都有了默认实现, 我们重点只看getApplicationContext:

1
2
3
4
5
6
7
// in file ContextImpl.java

@Override
public Context getApplicationContext() {
return (mPackageInfo != null) ?
mPackageInfo.getApplication() : mMainThread.getApplication();
}// mPackageInfo是LoadedApk类型

方法Activity.getApplication返回的是成员变量mApplication, 根据其构造过程, 是在attach时赋值的Application实例, 换句话说, 在同一个应用中时, Activity中调用getApplication跟调用getApplicationContext返回的是同一个对象, 这可以很方便地验证.

Context使用中需要注意的问题

前文中我们曾特地指出, Activity的Context跟Application的Context生命周期不同, 细心的同学可能要问了, 强调这个是因为有什么坑吗? 是的, 这里有坑.
原因就在于我们在创建组件的时候经常会用到Context这个参数, 有时为了图方便就直接传this, 大多数情况下也挺好使, 但其实这里存在很大的内存泄漏风险.
举个例子, 我们假设在Activity中创建了一个Bitmap, 为了在手机横竖屏切换的时候不至于重新装载这个资源, 我们将它写成static, 这样它的生命周期也就跟Activity.class相同了. 然后我们通过setBackgroundDrawable将它设置成了某个View(该View是通过new View(this)方法构造的实例)的背景. 我们知道, 在手机横竖屏切换的时候, 当前Activity会被销毁再重新创建, 因此此间会有一个gc标记过程. 这里存在什么问题呢? setBackgroundDrawable之后, 静态变量Bitmap会持有一个该View的引用, 而这个View持有一个Activity的引用, 这就导致Activity的引用计数不为0, gc过程无法回收该对象内存, 但是该对象已经不被任何过程使用, 于是就泄露了. 归根到底是因为创建View的时候使用了Activity作为Context, 但是该View由于存在被静态变量Bitmap引用的情况, 其实际生命周期要长于Activity.
解决这种泄露的方法是, 保证在Activity中使用Activitythis作为Context的任何对象, 其生命周期都不长于Activity本身, 否则应该考虑使用Application Context.
另外, 如果定义了非静态内部类, 那么对非静态内部类实例的引用也有可能导致外部类实例内存泄漏, 解决这种内存泄漏的方法是控制内部类的引用, 对于生命周期长于外部类的内部类, 应定义为静态内部类.

文章目录
  1. 1. Context接口和作用
  2. 2. Context默认实现
  3. 3. Context使用中需要注意的问题